﻿namespace Nomo.CoreModule
{
    /// <summary>
    /// 无符号自定义基整数。
    /// </summary>
    public readonly struct UBase : System.IEquatable<UBase>
    {
        /// <summary>
        /// 基，即数的进制。一般常见的基有 2 （计算机）、 8 （计算机）、 10 （通用）、 16 （计算机）和 60 （时间）。
        /// </summary>
        public readonly byte Base;

        /// <summary>
        /// 十进制值。
        /// </summary>
        public readonly ulong Value;

        private static readonly System.Collections.Generic.List<byte> Bits          = new System.Collections.Generic.List<byte>(64);
        private static readonly System.Text.StringBuilder             StringBuilder = new System.Text.StringBuilder(128);

        /// <summary>
        /// 使用指定的基和与其等效的十进制值初始化 <see cref="UBase"/> 类型的新实例。
        /// </summary>
        /// <param name="base">基。最小值为 2 .</param>
        /// <param name="value">与其等效的十进制值。</param>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="base"/> 小于 2 .</exception>
        public UBase(byte @base, ulong value)
        {
            if (@base < 2)
            {
                throw new System.ArgumentOutOfRangeException(nameof(@base));
            }
            Base  = @base;
            Value = value;
        }

        /// <summary>
        /// 使用指定的基和每一位的值初始化 <see cref="UBase"/> 类型的新实例。
        /// </summary>
        /// <param name="base">基。最小值为 2 .</param>
        /// <param name="bits">每一位的值，其中低位的索引比高位的索引更小。</param>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="base"/> 小于 2 .</exception>
        /// <exception cref="System.ArgumentNullException"><paramref name="bits"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="System.ArgumentException"><paramref name="bits"/> 提供的至少有一位的值超过了最大值。</exception>
        /// <exception cref="System.OverflowException">等效十进制值超过了 <see cref="ulong"/> 的最大值。</exception>
        public UBase(byte @base, System.Collections.Generic.IList<byte> bits)
        {
            if (@base < 2)
            {
                throw new System.ArgumentOutOfRangeException(nameof(@base));
            }
            if (bits == null)
            {
                throw new System.ArgumentNullException(nameof(bits));
            }
            var value  = 0UL;
            var weight = 1UL;
            var count  = bits.Count;
            for (var i = 0; i < count; i++)
            {
                var bit = bits[i];
                if (bit >= @base)
                {
                    throw new System.ArgumentException();
                }
                if (i != 0)
                {
                    checked
                    {
                        weight *= @base;
                    }
                }
                checked
                {
                    value += bit * weight;
                }
            }
            Base  = @base;
            Value = value;
        }

        /// <summary>
        /// 将每一位的值存入列表。
        /// </summary>
        /// <param name="bits">用来存放每一位的值的列表。</param>
        /// <param name="minimumDigit">最小总位数。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="bits"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="minimumDigit"/> 小于 0 .</exception>
        public void GetBits(System.Collections.Generic.List<byte> bits, int minimumDigit = 0)
        {
            GetBits(in this, bits, minimumDigit);
        }

        /// <summary>
        /// 将每一位的值存入列表。
        /// </summary>
        /// <param name="uBase">无符号自定义基整数。</param>
        /// <param name="bits">用于存放 <paramref name="uBase"/> 的每一位的值的列表。</param>
        /// <param name="minimumDigit">最小总位数。</param>
        /// <exception cref="System.ArgumentNullException"><paramref name="bits"/> 为 <see langword="null"/> .</exception>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="minimumDigit"/> 小于 0 .</exception>
        public static void GetBits(in UBase uBase, System.Collections.Generic.List<byte> bits, int minimumDigit = 0)
        {
            if (bits == null)
            {
                throw new System.ArgumentNullException(nameof(bits));
            }
            if (minimumDigit < 0)
            {
                throw new System.ArgumentOutOfRangeException();
            }
            bits.Clear();
            var value = uBase.Value;
            while (value != default)
            {
                bits.Add((byte) (value % uBase.Base));
                value /= uBase.Base;
            }
            while (bits.Count < minimumDigit)
            {
                bits.Add(default);
            }
        }

        /// <summary>
        /// 返回表示当前无符号自定义基整数的字符串。如果基是2、8、10、16，那么将会返回具有通用格式的字符串。
        /// </summary>
        /// <returns>表示当前无符号自定义基整数的字符串。</returns>
        public override string ToString()
        {
            return ToString(1);
        }

        /// <summary>
        /// 返回表示当前无符号自定义基整数的字符串。如果基是2、8、10、16，那么将会返回具有通用格式的字符串；否则，返回形如 [... ,n3, n2, n1, n0] (base) 的字符串。
        /// </summary>
        /// <param name="minimumDigit">最小总位数。</param>
        /// <returns>表示当前无符号自定义基整数的字符串。</returns>
        /// <exception cref="System.ArgumentOutOfRangeException"><paramref name="minimumDigit"/> 小于 1 .</exception>
        public string ToString(int minimumDigit)
        {
            if (minimumDigit < 1)
            {
                throw new System.ArgumentOutOfRangeException();
            }

            switch (Base)
            {
                case 2:
                    return ToBinaryString(minimumDigit);
                case 8:
                    return ToOctalString(minimumDigit);
                case 10:
                    return ToDecimalString(minimumDigit);
                case 16:
                    return ToHexadecimalString(minimumDigit);
                default:
                    return ToCustomBaseString(minimumDigit);
            }
        }

        private string ToBinaryString(int minimumDigit)
        {
            StringBuilder.Clear();
            StringBuilder.Append("0b");
            GetBits(in this, Bits, minimumDigit);
            for (var i = Bits.Count - 1; i >= 0; i--)
            {
                StringBuilder.Append((char) (Bits[i] + '0'));
            }
            return StringBuilder.ToString();
        }

        private string ToOctalString(int minimumDigit)
        {
            StringBuilder.Clear();
            StringBuilder.Append('0');
            GetBits(in this, Bits, minimumDigit);
            for (var i = Bits.Count - 1; i >= 0; i--)
            {
                StringBuilder.Append((char) (Bits[i] + '0'));
            }
            return StringBuilder.ToString();
        }

        private string ToDecimalString(int minimumDigit)
        {
            StringBuilder.Clear();
            GetBits(in this, Bits, minimumDigit);
            for (var i = Bits.Count - 1; i >= 0; i--)
            {
                StringBuilder.Append((char) (Bits[i] + '0'));
            }
            return StringBuilder.ToString();
        }

        private string ToHexadecimalString(int minimumDigit)
        {
            StringBuilder.Clear();
            StringBuilder.Append("0x");
            GetBits(in this, Bits, minimumDigit);
            for (var i = Bits.Count - 1; i >= 0; i--)
            {
                var bit = Bits[i];
                StringBuilder.Append((char) (bit < 10 ? bit + '0' : bit - 10 + 'A'));
            }
            return StringBuilder.ToString();
        }

        private string ToCustomBaseString(int minimumDigit)
        {
            StringBuilder.Clear();
            StringBuilder.Append('[');
            GetBits(in this, Bits, minimumDigit);
            for (var i = Bits.Count - 1; i >= 0; i--)
            {
                if (i != Bits.Count - 1)
                {
                    StringBuilder.Append(", ");
                }
                StringBuilder.Append(Bits[i]);
            }
            StringBuilder.Append(']');
            StringBuilder.Append(' ');
            StringBuilder.Append('(');
            StringBuilder.Append(Base);
            StringBuilder.Append(')');
            return StringBuilder.ToString();
        }

        /// <summary>
        /// 返回两个无符号自定义基整数相加的结果。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>两个无符号自定义基整数相加的结果。</returns>
        /// <exception cref="System.InvalidOperationException"><paramref name="uBase1"/> 和 <paramref name="uBase2"/> 的基不相等。</exception>
        public static UBase operator +(UBase uBase1, UBase uBase2)
        {
            var @base = uBase1.Base;
            if (@base == uBase2.Base)
            {
                return new UBase(@base, uBase1.Value + uBase2.Value);
            }
            throw new System.InvalidOperationException();
        }

        /// <summary>
        /// 返回两个无符号自定义基整数相减的结果。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>两个无符号自定义基整数相减的结果。</returns>
        /// <exception cref="System.InvalidOperationException"><paramref name="uBase1"/> 和 <paramref name="uBase2"/> 的基不相等。</exception>
        public static UBase operator -(UBase uBase1, UBase uBase2)
        {
            var @base = uBase1.Base;
            if (@base == uBase2.Base)
            {
                return new UBase(@base, uBase1.Value - uBase2.Value);
            }
            throw new System.InvalidOperationException();
        }

        /// <summary>
        /// 返回无符号自定义基整数和无符号 64 位整数相加的结果。
        /// </summary>
        /// <param name="uBase">无符号自定义基整数。</param>
        /// <param name="value">无符号 64 位整数。</param>
        /// <returns>无符号自定义基整数和无符号 64 位整数相加的结果。</returns>
        public static UBase operator +(UBase uBase, ulong value)
        {
            return new UBase(uBase.Base, uBase.Value + value);
        }

        /// <summary>
        /// 返回无符号自定义基整数和无符号 64 位整数相减的结果。
        /// </summary>
        /// <param name="uBase">无符号自定义基整数。</param>
        /// <param name="value">无符号 64 位整数。</param>
        /// <returns>无符号自定义基整数和无符号 64 位整数相减的结果。</returns>
        public static UBase operator -(UBase uBase, ulong value)
        {
            return new UBase(uBase.Base, uBase.Value - value);
        }

        /// <summary>
        /// 返回一个值，这个值表示两个无符号自定义基整数是否被视为相等。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>两个无符号自定义基整数是否被视为相等。</returns>
        public static bool operator ==(UBase uBase1, UBase uBase2)
        {
            return uBase1.Base == uBase2.Base && uBase1.Value == uBase2.Value;
        }

        /// <summary>
        /// 返回一个值，这个值表示两个无符号自定义基整数是否被视为不相等。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>两个无符号自定义基整数是否被视为不相等。</returns>
        public static bool operator !=(UBase uBase1, UBase uBase2)
        {
            return uBase1.Base != uBase2.Base || uBase1.Value != uBase2.Value;
        }

        /// <summary>
        /// 返回一个值，这个值表示第一个无符号自定义基整数是否被视为小于第二个无符号自定义基整数。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>第一个无符号自定义基整数是否被视为小于第二个无符号自定义基整数。</returns>
        public static bool operator <(UBase uBase1, UBase uBase2)
        {
            if (uBase1.Base == uBase2.Base)
            {
                return uBase1.Value < uBase2.Value;
            }
            throw new System.InvalidOperationException();
        }

        /// <summary>
        /// 返回一个值，这个值表示第一个无符号自定义基整数是否被视为大于第二个无符号自定义基整数。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>第一个无符号自定义基整数是否被视为大于第二个无符号自定义基整数。</returns>
        public static bool operator >(UBase uBase1, UBase uBase2)
        {
            if (uBase1.Base == uBase2.Base)
            {
                return uBase1.Value > uBase2.Value;
            }
            throw new System.InvalidOperationException();
        }

        /// <summary>
        /// 返回一个值，这个值表示第一个无符号自定义基整数是否被视为小于第二个无符号自定义基整数，或被视为与第二个无符号自定义基整数相等。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>第一个无符号自定义基整数是否被视为小于第二个无符号自定义基整数，或被视为与第二个无符号自定义基整数相等。</returns>
        public static bool operator <=(UBase uBase1, UBase uBase2)
        {
            if (uBase1.Base == uBase2.Base)
            {
                return uBase1.Value <= uBase2.Value;
            }
            throw new System.InvalidOperationException();
        }

        /// <summary>
        /// 返回一个值，这个值表示第一个无符号自定义基整数是否被视为大于第二个无符号自定义基整数，或被视为与第二个无符号自定义基整数相等。
        /// </summary>
        /// <param name="uBase1">第一个无符号自定义基整数。</param>
        /// <param name="uBase2">第二个无符号自定义基整数。</param>
        /// <returns>第一个无符号自定义基整数是否被视为大于第二个无符号自定义基整数，或被视为与第二个无符号自定义基整数相等。</returns>
        public static bool operator >=(UBase uBase1, UBase uBase2)
        {
            if (uBase1.Base == uBase2.Base)
            {
                return uBase1.Value >= uBase2.Value;
            }
            throw new System.InvalidOperationException();
        }

        /// <inheritdoc />
        public bool Equals(UBase other)
        {
            return this == other;
        }

        /// <inheritdoc />
        public override bool Equals(object obj)
        {
            return obj is UBase other && Equals(other);
        }

        /// <inheritdoc />
        public override int GetHashCode()
        {
            unchecked
            {
                return (Base.GetHashCode() * 397) ^ Value.GetHashCode();
            }
        }
    }
}
